/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-13
 * V4.0
 */
package	com.jphenix.webserver.servlet.jsp;

import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.webserver.servlet.ASuperServlet;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * JSP解析类
 * com.jphenix.webserver.servlet.jsp.JSPServlet
 * 
 * 2019-09-17 整理了代码格式
 * 
 * @author 刘虻
 * 2009-11-18上午11:10:12
 */
@ClassInfo({"2019-09-17 10:42","JSP解析类"})
public class JSPServlet extends ASuperServlet {
	
	/**
	 * 串行标识
	 */
	private static final long serialVersionUID = 4690517039156405084L;
	
	protected String[]                  compiler;
	protected File                      repository;
	protected String                    defaultContentType   = "text/html charset=UTF-8";
	private   boolean                   correctlyInitialized = false;
	private   boolean			        checkDependencies    = true;
	private   boolean			        debug                = false;
	private   JSPClassLoader	        jspClassLoader;
	private   Hashtable<String,Servlet> jspServlets;

	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2009-11-18下午03:16:35
	 */
	@Override
    public void init(ServletConfig config) throws ServletException {
		super.init(config);
		String				s;
		Vector<Object>				v;
		StringTokenizer		toker;

		// get repository where generated java and class files are stored
		s = config.getInitParameter("repository");
		if(s == null) {
			s = "/../../../work";
			//throw new ServletException("mandatory init parameter repository has not been specified");
		}
		repository = new File(getServ().getFilesUtil().getAllFilePath(s));
		try {
			repository = new File(repository.getCanonicalPath());
		} catch(IOException ioexc) {
			repository = new File(repository.getAbsolutePath());
		}

		// get compiler command line
		s = config.getInitParameter("compiler");
		if(s == null) {
			s = "builtin-javac -classpath %classpath%" + File.pathSeparator +
				"%repository% -d %repository% -deprecation %source%";
		}
		v = new Vector<Object>();
		toker = new StringTokenizer(s);
		while(toker.hasMoreTokens()) {
			v.addElement(toker.nextToken());
		}
		if(v.size() <= 1) {
			throw new ServletException("init parameter compiler does not specify the compiler's parameters");
		}
		compiler = new String[v.size()];
		v.copyInto(compiler);		

		// get check dependency settings
		s = config.getInitParameter("checkdependencies");
		if(s != null && ("false".equalsIgnoreCase(s) || "no".equalsIgnoreCase(s))) {
			checkDependencies = false;
		}

		// get debug setting
		s = config.getInitParameter("debug");
		if(s != null && ("true".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s))) {
			debug = true;
		}

		// get default Content-Type
		s = config.getInitParameter("defaultcontenttype");
		if(s != null) {
			defaultContentType = s;
		}
		jspClassLoader       = new JSPClassLoader(repository, config, debug);
		jspServlets          = new Hashtable<String,Servlet>();
		correctlyInitialized = true;
	}

	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2009-11-18下午03:16:21
	 */
	@Override
    @SuppressWarnings("deprecation")
    protected void service(
			HttpServletRequest request
			, HttpServletResponse response) throws IOException, ServletException {
		File			jspFile;
		String			className;
		Servlet			srv;
		JSPCompiler		jspCompiler;
		if(!correctlyInitialized) {
			throw new ServletException(
					"This servlet has not been initialized correctly");
		}
		try {
			jspFile = new File(request.getRealPath(request.getPathInfo()));
			try {
				jspFile = new File(jspFile.getCanonicalPath());
			} catch(IOException ioexc) {
				jspFile = new File(jspFile.getAbsolutePath());
			}
			className = JSPCompiler.getClassNameForJspFile(jspFile);
			// try to load jsp servlet from cache or class file
			srv = jspServlets.get(className);
			if(srv == null) {
				srv = loadServlet(className);
			}

			// unload servlet if dependencies demand it
			if(checkDependencies && srv != null) {
				if(checkServletDependencies(srv.getClass())) {
					unloadServlet(srv);
					refreshClassLoader(srv.getClass());
					srv = null;
				}
			}

			// compile & load jsp servlet if we haven't loaded it yet
			if(srv == null) {
				jspCompiler = new JSPCompiler(this, jspFile, className, request);
				jspCompiler.compile();

				srv = loadServlet(className);
				if(srv == null) {
					throw new JSPException("Could not load jsp servlet class " + className);
				}
			}
		} catch(JSPException jspexc) {
			if(debug) {
				getServletConfig().getServletContext().log(jspexc.getHttpErrorCode() + " JSP compile-time error: " + jspexc.getMessage());
			}
			response.setStatus(jspexc.getHttpErrorCode(), "JSP compile-time error");
			response.setContentType("text/html");
			PrintWriter out = response.getWriter();
			out.println("<HTML><HEAD><TITLE>" + jspexc.getHttpErrorCode() + " JSP compile-time error</TITLE></HEAD><BODY>" +
						"<H2>" + jspexc.getHttpErrorCode() + " JSP compile-time error</H2>" +
						"The JSP page you requested could not be served because the following error(s) occured:<BR><PRE>");
			out.println(jspexc.getMessage());
			out.println("</PRE></BODY></HTML>");
			out.close();
			return;
		}
		// run jsp servlet
		srv.service(request, response);
	}

	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2009-11-18下午03:16:09
	 */
	@Override
    public void destroy() {
		super.destroy();
		if(correctlyInitialized) {
			Enumeration<Servlet>		e;
			Servlet			srv;
			e = jspServlets.elements();
			while(e.hasMoreElements()) {
				srv = e.nextElement();
				unloadServlet(srv);
			}
		}
	}

	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2009-11-18下午03:16:01
	 */
	@Override
    public String getServletInfo() {
		return "GNUJSP JSPServlet";
	}

	/**
	 * 加载指定类
	 * @author 刘虻
	 * 2009-11-18下午03:14:49
	 * @param className 类名
	 * @return 类实例
	 * @throws JSPException 执行发生异常
	 */
	protected Servlet loadServlet(String className) throws JSPException {
		Class<?> c; //构建返回类
		Field fld;//版本号变量
		Servlet srv; //返回值
		// load servlet class
		try {
			c = jspClassLoader.loadClass(className);
			if(c == null) {
                return null;
            }
		} catch(ClassNotFoundException cnfexc) {
			return null;
		}
		// check compiler version dependency
		try {
			fld = c.getField("__compilerVersionNr");
			if(fld.getInt(null) != JSPCompiler.COMPILER_VERSION_NR) {
				refreshClassLoader(c);
				return null;
			}
		} catch(IllegalAccessException fiaexc) {
			throw new JSPException("Jsp servlet class " + className + " does not have a public static __compilerVersionNr field");
		} catch(NoSuchFieldException nsfexc) {
			throw new JSPException("Jsp servlet class " + className + " does not have a __compilerVersionNr field of type int");
		} catch(SecurityException secexc) {
			throw new JSPException("Jsp servlet class " + className + " has a security problem with its __compilerVersionNr field");
		}
		// check servlet's other dependencies
		if(checkDependencies && checkServletDependencies(c)) {
			refreshClassLoader(c);
			return null;
		}
		// instantiate servlet
		try {
			srv = (Servlet) c.newInstance();
		} catch(IllegalAccessException iaexc) {
			throw new JSPException("Could not instantiate jsp servlet class " + className + " because of an illegal access exception");
		} catch(InstantiationException iexc) {
			throw new JSPException("Could not instantiate jsp servlet class " + className + " because it is an interface or an abstract class");
		}
		// init servlet
		try {
			srv.init(getServletConfig());
		} catch(Exception exc) {}
		jspServlets.put(className, srv);
		return srv;
	}

	/**
	 * 卸载指定类
	 * @author 刘虻
	 * 2009-11-18下午03:14:31
	 * @param srv 指定类
	 */
	protected void unloadServlet(Servlet srv) {
		try {
			srv.destroy();
		} catch(Exception exc) { }
		jspServlets.remove(srv.getClass().getName());
	}

	/**
	 * 母鸡
	 * @author 刘虻
	 * 2009-11-18下午03:13:06
	 * @param cls 指定类
	 * @return 获取调用值
	 * @throws JSPException 执行发生异常
	 */
	protected boolean checkServletDependencies(Class<?> cls) throws JSPException {
		try {
			// invoke dependency checking method
			Method meth = cls.getMethod("__checkDependencies");
			return ((Boolean) meth.invoke(null, new Object[0])).booleanValue();
		} catch(IllegalAccessException iaexc) {
			throw new JSPException("Jsp servlet class " + cls.getName() + " does not have a public static __checkDependencies() method");
		} catch(InvocationTargetException itexc) {
			throw new JSPException("Jsp servlet class " + cls.getName() + "'s __checkDependencies() method threw an exception: " + itexc.getTargetException());
		} catch(NoSuchMethodException nsfexc) {
			throw new JSPException("Jsp servlet class " + cls.getName() + " does not have a __checkDependencies() method that returns a boolean");
		} catch(SecurityException secexc) {
			throw new JSPException("Jsp servlet class " + cls.getName() + " has a security problem with its __checkDependencies() method");
		}
	}

	/**
	 * 刷新类加载器
	 * @author 刘虻
	 * 2009-11-18下午03:12:22
	 * @param cls 指定类
	 */
	protected void refreshClassLoader(Class<?> cls) {
		if(cls.getClassLoader()==jspClassLoader) {
			jspClassLoader = 
				new JSPClassLoader(
						repository, getServletConfig(), debug);
		}
	}

	/**
	 * 解析URL编码
	 * @author 刘虻
	 * 2009-11-18下午03:12:02
	 * @param val 源URL
	 * @return 解析后的URL
	 */
	protected static String urlDecode(String val) {
        StringBuffer buf = new StringBuffer(val.length());
        char c;
		for (int i = 0; i < val.length(); i++) {
			c = val.charAt(i);
			if(c == '%') {
				try {
					buf.append((char)Integer.parseInt(val.substring(i+1,i+3),16));
					i += 2;
					continue;
				} catch(Exception e) { }
			} else if(c == '+') {
				buf.append(' ');
				continue;
			}
 			buf.append(c);
		}
        return buf.toString();
	}
}
